using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;

namespace mvcincode.Middlewares;

public class MyAuthMiddleware
{
    private readonly ILogger<MyAuthMiddleware> _logger;
    private readonly RequestDelegate _next;
    private readonly IAuthorizationPolicyProvider _policyProvider;
    private readonly IPolicyEvaluator _policyEvaluator;

    public MyAuthMiddleware(
        ILogger<MyAuthMiddleware> logger,
        RequestDelegate next,
        IAuthorizationPolicyProvider policyProvider,
        IPolicyEvaluator policyEvaluator)
    {
        this._logger = logger;
        this._next = next;
        this._policyProvider = policyProvider;
        this._policyEvaluator = policyEvaluator;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var endpoint = httpContext.GetEndpoint();
        // 获得标注了Authorization的注解的数据集合
        var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
        var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);

        if (policy == null)
        {
            await _next(httpContext);
            return;
        }

        // policy.Requirements
        // policy.AuthenticationSchemes
        _logger.LogInformation($"authorizeData.Count: {authorizeData.Count}");
        _logger.LogInformation($"policy.Requirements.Count: {policy.Requirements.Count}");

        // 验证方案, 例如有Cookies, Bearer等
        // 指:[Authorize(AuthenticationSchemes = "Bearer")]
        policy.AuthenticationSchemes.ToList()
            .ForEach(scheme => _logger.LogInformation(scheme));


        // IAuthorizationRequirement是一个标识接口
        // IAuthorizationRequirement

        // IPolicyEvaluator的实现类是:PolicyEvaluator
        // 这里也可以通过注入的方式获取IPolicyEvaluator的实现
        // var policyEvaluator = httpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
        // var authenticateResult = await _policyEvaluator.AuthenticateAsync(policy, httpContext);

        var authenticateResult = httpContext.Features.Get<IAuthenticateResultFeature>()?.AuthenticateResult
            ?? DefaultAuthenticateResult(httpContext);

        // httpContext.ChallengeAsync()

        ClaimsPrincipal newPrinciple = null;

        foreach (var scheme in policy.AuthenticationSchemes)
        {
            var result = await httpContext.AuthenticateAsync(scheme);
            if (result != null && result.Succeeded)
            {
                newPrinciple = MergeUserPrincipal(newPrinciple, result.Principal);
            }
        };

        if (newPrinciple != null)
        {
            var claims = newPrinciple.Claims;
            _logger.LogInformation($"claims.count: {claims.Count()}");
        }

        // httpContext.Features.ToList()
        //     .ForEach(f =>
        //     {
        //         _logger.LogInformation($"feature key:{f.Key},value:{f.Value}");
        //     });

        await _next(httpContext);


        static AuthenticateResult DefaultAuthenticateResult(HttpContext context)
        {
            return (context.User?.Identity?.IsAuthenticated ?? false)
                ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
                : AuthenticateResult.NoResult();
        }

        static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal existingPrincipal, ClaimsPrincipal additionalPrincipal)
        {
            // For the first principal, just use the new principal rather than copying it
            if (existingPrincipal == null && additionalPrincipal != null)
            {
                return additionalPrincipal;
            }

            var newPrincipal = new ClaimsPrincipal();

            // New principal identities go first
            if (additionalPrincipal != null)
            {
                newPrincipal.AddIdentities(additionalPrincipal.Identities);
            }

            // Then add any existing non empty or authenticated identities
            if (existingPrincipal != null)
            {
                newPrincipal.AddIdentities(existingPrincipal.Identities.Where(i => i.IsAuthenticated || i.Claims.Any()));
            }
            return newPrincipal;
        }
    }
}
